package com.director.hibernate.json.impl.gson;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import org.hibernate.EntityMode;
import org.hibernate.Hibernate;
import org.hibernate.SessionFactory;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.type.Type;

import java.io.Serializable;

/**
 * Specific serializer for Hibernate entities that exclude from the serialization process all lazies
 * and uninitialized association property values.
 *
 * @author Simone Ricciardi
 * @version 1.0, 10/15/2011
 */
public class EntitySerializer implements JsonSerializer {

   private final SessionFactory sessionFactory;

   public EntitySerializer(SessionFactory sessionFactory) {
      this.sessionFactory = sessionFactory;
   }

   @Override
   public JsonElement serialize(Object src, java.lang.reflect.Type typeOfSrc, JsonSerializationContext context) {

      if(src == null) {
         return context.serialize(src);
      }

      if(HibernateProxy.class.isInstance(src)) {
         LazyInitializer lazyInitializer = ((HibernateProxy) src).getHibernateLazyInitializer();
         if(lazyInitializer.isUninitialized()) {
            lazyInitializer.initialize();
         }
         src = lazyInitializer.getImplementation();
      }

      ClassMetadata classMetadata = this.sessionFactory.getClassMetadata(src.getClass());

      JsonObject jsonObject = new JsonObject();

      String idName = classMetadata.getIdentifierPropertyName();
      Serializable idValue =classMetadata.getIdentifier(src, (SessionImplementor)this.sessionFactory.getCurrentSession());
      jsonObject.add(idName,context.serialize(idValue));

      String[] propertyNames = classMetadata.getPropertyNames();
      for(String propertyName : propertyNames) {
         Type propertyType = classMetadata.getPropertyType(propertyName);
         Object propertyValue = classMetadata.getPropertyValue(src, propertyName, EntityMode.POJO);
         if(this.isUninitializedAssociation(propertyType, propertyValue)) {
            continue;
         }
         jsonObject.add(propertyName, context.serialize(propertyValue, propertyType.getReturnedClass()));
      }
      return jsonObject;
   }

   private boolean isUninitializedAssociation(Type propertyType, Object propertyValue) {
      return (propertyType.isAssociationType() || propertyType.isCollectionType())
            && !Hibernate.isInitialized(propertyValue);
   }
}
